﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml.XPath;

using umbraco;
using umbraco.blog4.DataTypes.DoctypeProvider;
#if UMB45
using umbraco.blog4.DataTypes.GroupedTags;
#else
using umbraco.cms.businesslogic.Tags;
#endif
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.datatype;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.web;
using umbraco.editorControls.tags;

#if UMB45
using umbraco.presentation.nodeFactory;
#else
using umbraco.NodeFactory;
using umbraco.interfaces;
#endif

namespace umbraco.blog4 {

    public class Blog : ApplicationBase {
        private Document _blog;

        public const string DTDefault_DateFolder = "DateFolder";

        #region .ctor
        public Blog() {
            Document.BeforePublish += Document_BeforePublish;
            Document.AfterPublish += Document_AfterPublish;
            Document.New += Document_New;

            DocumentType.AfterDelete += DocumentType_AfterDelete;
            DocumentType.AfterMove += DocumentType_AfterMove;
            DocumentType.AfterSave += DocumentType_AfterSave;
        }

        public Blog(int blogId) {
            _blog = new Document(blogId);
        }

        public Blog(Document blog)
        {
            _blog = blog;
        }
        public Blog(string blogName, int parentNode) {
            _blog = Document.MakeNew(blogName, DocumentType.GetByAlias(DocumentTypeName), User.GetCurrent(), parentNode);
        }
        public Blog(string blogName, string docType, int parentNode) {
            _blog = Document.MakeNew(blogName, DocumentType.GetByAlias(docType), User.GetCurrent(), parentNode);
        }
        public Blog(string blogName, string docType, int userId, int parentNode) {
            _blog = Document.MakeNew(blogName, DocumentType.GetByAlias(docType), User.GetUser(userId), parentNode);
        }
        public Blog(string blogName, DocumentType docType, int userId, int parentNode) {
            _blog = Document.MakeNew(blogName, docType, new User(userId), parentNode);
        }
        public Blog(string blogName, DocumentType docType, User user, Document parent) {
            _blog = Document.MakeNew(blogName, docType, user, parent.Id);
        }
        public Blog(string blogName, DocumentType docType, User user, int parentNode) {
            _blog = Document.MakeNew(blogName, docType, user, parentNode);
        }

        #endregion

        #region Eventhandlers

        void DocumentType_AfterSave(DocumentType sender, SaveEventArgs e)
        {
            BlogLibrary.ClearDocumentTypeCache();
        }

        void DocumentType_AfterMove(object sender, MoveEventArgs e)
        {
            BlogLibrary.ClearDocumentTypeCache();
        }

        void DocumentType_AfterDelete(DocumentType sender, DeleteEventArgs e)
        {
            BlogLibrary.ClearDocumentTypeCache();
        }

        void Document_New(Document sender, NewEventArgs e) {
            if (BlogLibrary.IsType(DocumentTypeName, sender.Id)) {
                //Blog b = new Blog(sender);
                int userId = -1;
                /* 
                if (sender.getProperty("owner") != null && !string.IsNullOrEmpty(sender.getProperty("owner").Value.ToString())) {
                    
                } */

                if (int.TryParse(sender.getProperty("owner").Value.ToString(), out userId)) {
                    if (userId >= 0) {
                        User author = User.GetUser(userId);
                        if (author != null && !author.Disabled) {
                            //Does the author have a channel setup? If the author already have a channel, we won't change it.
                            umbraco.presentation.channels.businesslogic.Channel channel = null;
                            try {
                                channel = new umbraco.presentation.channels.businesslogic.Channel(author.Id);
                            } catch {
                                channel = null;
                            }

                            if (channel == null) {
                                //now we can setup the channel for this author and this blog
                                channel = new umbraco.presentation.channels.businesslogic.Channel();
                                channel.Name = sender.Text;
                                channel.User = author;
                                channel = new umbraco.presentation.channels.businesslogic.Channel();
                                channel.DocumentTypeAlias = "BlogPost";
                                channel.FieldCategoriesAlias = "tags";
                                channel.FieldDescriptionAlias = "bodyText";
                                channel.FieldExcerptAlias = "";
                                channel.FullTree = true;
                                channel.StartNode = sender.Id;

                                //we will also setup a media folder for storing all images in, in the media library
                                Media m = Media.MakeNew(author.Name + "'s blog photos", MediaType.GetByAlias("Folder"), author, author.StartMediaId);
                                m.Save();

                                channel.ImageSupport = true;
                                channel.MediaFolder = m.Id;
                                channel.MediaTypeAlias = "Image";
                                channel.MediaTypeFileProperty = "umbracoFile";
                                channel.Save();
                            }
                        }
                    }
                }
            }
        }

        void Document_AfterPublish(Document sender, PublishEventArgs e) { }

        void Document_BeforePublish(Document sender, PublishEventArgs e) { }

        #endregion

        #region Properties
        /// <summary>
        /// The name of the <see cref="Blog" />.
        /// </summary>
        public string BlogName {
            get { return _blog.Text; }
            set { _blog.Text = value; _blog.Save(); }
        }
        /// <summary>
        /// The node ID of the <see cref="Blog" />.
        /// </summary>
        public int Id { get { return _blog.Id; } }
        /// <summary>
        /// The Umbraco-generated URL of the <see cref="Blog" />.
        /// </summary>
        public string Url { get { return umbraco.library.NiceUrl(Id); } }
        /// <summary>
        /// The date-time format string to be used when generating blog post URLs.
        /// </summary>
        public string EntryDateUrlFormat {
            get { return _blog.getProperty("blogPostDateFolderFormat").Value.ToString(); }
            set { _blog.getProperty("blogPostDateFolderFormat").Value = value; _blog.Save(); _blog.Publish(User.GetCurrent()); }
        }
        public string TagGroup {
            get {
                var prevalues = ((PrevalueEditor)DTBlogPost.getPropertyType("tags").DataTypeDefinition.DataType.PrevalueEditor).Prevalues;
                if (prevalues.Count > 0) return prevalues["group"].ToString();
                return "default";
            }
            set {
                PrevalueEditor pv = (PrevalueEditor)DTBlogPost.getPropertyType("tags").DataTypeDefinition.DataType.PrevalueEditor;
                if (pv.Prevalues.Count > 0) {
                    pv.Prevalues["group"] = value;
                    pv.Save();
                }
            }
        }

        /// <summary>
        /// Gets or Sets the Blog Category
        /// </summary>
        /// <remarks>
        /// The Category is used to determine which Tag Category to use for the posts within the blog.
        /// This way we can have multiple blogs on a site with a different category for each to 
        /// distinquish between the tags in each blog.
        /// 
        /// This is different from using the category in the BlogPost document type's tag, because it restricts us from
        /// using the same document type for multiple blogs if we want to have different categories for each.
        /// </remarks>
        public string Category
        {
            get { return _blog.getProperty("category").Value.ToString(); }
            set { _blog.getProperty("category").Value = value; _blog.Save(); }
        }
        public bool DisableComments
        {
            get { return _blog.getProperty("disableComments").Value.ToString() == "1"; }
            set { _blog.getProperty("disableComments").Value = value ? 1 : 0; _blog.Save(); }
        }

        public bool ModerateComments
        {
            get { return _blog.getProperty("moderateComments").Value.ToString() == "1"; }
            set { _blog.getProperty("moderateComments").Value = value ? 1 : 0; _blog.Save(); }
        }

        /// <summary>
        /// A List of Post objects.
        /// </summary>
        public IEnumerable<Post> Posts {
            get {
                //List<Post> p = new List<Post>();

                XPathNodeIterator xn = umbraco.library.GetXmlNodeByXPath(String.Format(@"//* [(@isDoc or name()='node') and @id={0}]//* [(@isDoc or name()='node')]", Id));
                while (xn.MoveNext()) {
                    if (BlogLibrary.IsType(Post.DocumentTypeName, Convert.ToInt32(xn.Current.GetAttribute("id", String.Empty))))
                        yield return new Post(Convert.ToInt32(xn.Current.GetAttribute("id", String.Empty)));
                }
                //return p;
            }
        }
        /// <summary>
        /// Retrieve all Comments associated with this Blog
        /// </summary>
        public IEnumerable<Comment> Comments
        {
            get {
                foreach (var post in Posts)
                    foreach(var comment in post.Comments)
                        yield return comment;
            }
        }
        /// <summary>
        /// Retrieve all TrackBacks associated with this Blog.
        /// </summary>
        public IEnumerable<TrackBack> TrackBacks
        {
            get {
                foreach (var post in Posts)
                    foreach (var trackBack in post.TrackBacks)
                        yield return trackBack;
            }
        }

        /// <summary>
        /// Retrieves the Blog that the current Node belongs to.
        /// </summary>
        /// <param name="node">Starting Document</param>
        public static Blog Current(Document node)
        {
            Document n = node;
            while (n.Level != 1 && !BlogLibrary.TypeAliasInherits(DocumentTypeName, n.ContentType.Alias)) { n = new Document(n.Parent.Id); }
            return BlogLibrary.TypeAliasInherits(DocumentTypeName, n.ContentType.Alias) ? new Blog(n.Id) : null;
        }

        /// <summary>
        /// Retrieves the Blog that the current Node belongs to.
        /// </summary>
        /// <param name="nodeId">Id of the starting Document</param>
        public static Blog Current(int nodeId)
        {
            return Current(new Document(nodeId));
        }

        public static string DocumentTypeName { get { return "Blog"; } }
        #endregion

        #region Document type properties
        //public DocumentType DTBlog { get { return DocumentType.GetByAlias(_blog.ContentType.Alias); } }
        public DocumentType DTBlogPost { get { return GetDocumentTypeFromProperty(Id, "dtBlogPost", Post.DocumentTypeName); } }
        public DocumentType DTBlogTextPage { get { return GetDocumentTypeFromProperty(Id, "dtBlogTextPage", "BlogTextpage"); } }

        // TODO: Evaluate whether there is anything to be gained from using custom document types for Comment and Trackback -- we can use XSLT/Razor for rendering the content, so no need for a Template as such...
        public DocumentType DTBlogComment { get { return GetDocumentTypeFromProperty(Id, "dtBlogComment", Comment.DocumentTypeName); } }
        public DocumentType DTBlogTrackBack { get { return GetDocumentTypeFromProperty(Id, "dtBlogTrackBack", TrackBack.DocumentTypeName); } }
        public DocumentType DTBlogDateFolder { get { return GetDocumentTypeFromProperty(Id, "dtBlogDateFolder", DTDefault_DateFolder); } }

        private static DocumentType GetDocumentTypeFromProperty(int blogId, string name, string defaultValue = "")
        {
            // This should be faster than having to query the database.
            Node blog = new Node(blogId);
            var property = blog.GetProperty(name);
            if (property == null || property.Value == null)
                return DocumentType.GetByAlias(defaultValue);

            int docTypeId = DoctypeXsltExtensions.GetValue(property.Value);
            if (docTypeId > 0)
                return new DocumentType(docTypeId);
            else
                return DocumentType.GetByAlias(defaultValue);

        }
        #endregion

        #region XSLT helpers
        public static XPathNodeIterator XmlCurrent(int nodeId) {
            // The disabled line is way too expensive for rendering XSLT macros as it results in SQL database calls...
            //Blog b = Blog.Current(nodeId);
            INode n = new Node(nodeId);
            while (!BlogLibrary.TypeAliasInherits(Blog.DocumentTypeName, n.NodeTypeAlias) && n.Parent != null)
                n = n.Parent;

            if (BlogLibrary.TypeAliasInherits(Blog.DocumentTypeName, n.NodeTypeAlias))
                return umbraco.library.GetXmlNodeById(n.Id.ToString());

            return null;
        }

        public static XPathNodeIterator XmlComments(int nodeId) {
            return umbraco.library.GetXmlNodeByXPath(
                String.Format(
                    "//*[(@isDoc or name()='node') and (name()='{0}' or @nodeTypeAlias='{0}')]",
                    Blog.Current(nodeId).DTBlogComment.Alias
                )
            );
        }

        public static string GetTagGroup(int blogId) {
            Blog b = Blog.Current(blogId);
            return b.TagGroup;
        }
        #endregion
    }
}